#include "gtest_mpi_listener.hpp"
#include "mpi/mpi_util.hpp"
#include <gtest/gtest.h>

#define COMM_WORLD_ROOT 0

TEST(MPI_STRING_TEST, SendRecv) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int SEND_RANK = 0;
  static constexpr int RECV_RANK = 1;

  std::string str = "";
  switch (rank) {
  case SEND_RANK:
    str = "hello world";
    MPI_Send_String(str, RECV_RANK, 0, MPI_COMM_WORLD);
    break;
  case RECV_RANK:
    MPI_Recv_String(str, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("hello world").size());
    ASSERT_STREQ(str.c_str(), "hello world");
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, SendRecvVec) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr int SEND_RANK = 0;
  static constexpr int RECV_RANK = 1;

  std::vector<std::string> strs;
  switch (rank) {
  case SEND_RANK:
    strs.resize(VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] = std::string("hello world ") + std::to_string(i);
    }
    MPI_Send_Strings(strs, RECV_RANK, 0, MPI_COMM_WORLD);
    break;
  case RECV_RANK:
    MPI_Recv_Strings(strs, VEC_SIZE, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected = std::string("hello world ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, IsendRecv) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int SEND_RANK = 0;
  static constexpr int RECV_RANK = 1;

  std::string str1 = "";
  std::string str2 = "";
  switch (rank) {
  case SEND_RANK:
    MPI_Request req1, req2;
    str1 = "hello";
    str2 = "world";
    MPI_Isend_String(str1, RECV_RANK, 0, MPI_COMM_WORLD, &req1);
    MPI_Isend_String(str2, RECV_RANK, 0, MPI_COMM_WORLD, &req2);

    MPI_Wait(&req2, MPI_STATUS_IGNORE);
    MPI_Wait(&req1, MPI_STATUS_IGNORE);
    break;
  case RECV_RANK:
    MPI_Recv_String(str1, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str1.size(), std::string("hello").size());
    ASSERT_STREQ(str1.c_str(), "hello");
    MPI_Recv_String(str2, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str2.size(), std::string("world").size());
    ASSERT_STREQ(str2.c_str(), "world");
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, IsendRecv2) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 3);

  enum COMM { SENDER = 0, RECVER1, RECVER2 };

  std::string str1 = "";
  std::string str2 = "";
  switch (rank) {
  case SENDER: {
    MPI_Request reqs[2];
    str1 = "hello";
    str2 = "world";
    MPI_Isend_String(str1, RECVER1, 0, MPI_COMM_WORLD, &reqs[0]);
    MPI_Isend_String(str2, RECVER2, 0, MPI_COMM_WORLD, &reqs[1]);

    MPI_Waitall(2, reqs, MPI_STATUS_IGNORE);
  } break;
  case RECVER1:
    MPI_Recv_String(str1, SENDER, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str1.size(), std::string("hello").size());
    ASSERT_STREQ(str1.c_str(), "hello");
    break;
  case RECVER2:
    MPI_Recv_String(str2, SENDER, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str2.size(), std::string("world").size());
    ASSERT_STREQ(str2.c_str(), "world");
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, IsendRecvVec) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr int SEND_RANK = 0;
  static constexpr int RECV_RANK = 1;

  std::vector<std::string> strs;
  switch (rank) {
  case SEND_RANK: {
    MPI_Request reqs[VEC_SIZE];
    strs.resize(VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] = std::string("hello world ") + std::to_string(i);
    }
    MPI_Isend_Strings(strs, RECV_RANK, 0, MPI_COMM_WORLD, reqs);

    MPI_Waitall(VEC_SIZE, reqs, MPI_STATUSES_IGNORE);
  } break;
  case RECV_RANK:
    MPI_Recv_Strings(strs, VEC_SIZE, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected = std::string("hello world ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, PackIsendRecvVec) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr int SEND_RANK = 1;
  static constexpr int RECV_RANK = 0;

  std::vector<std::string> strs;
  switch (rank) {
  case SEND_RANK: {
    std::string pack_buff;
    int size_buff[VEC_SIZE];

    MPI_Request req;
    strs.resize(VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] = std::string("hello world ") + std::to_string(i);
    }
    MPI_PackIsend_Strings(strs, pack_buff, size_buff, RECV_RANK, 0,
                          MPI_COMM_WORLD, &req);

    MPI_Wait(&req, MPI_STATUS_IGNORE);
  } break;
  case RECV_RANK:
    MPI_PackRecv_Strings(strs, VEC_SIZE, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected = std::string("hello world ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, PackIsendRecvVec2) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr int SEND_RANK = 1;
  static constexpr int RECV_RANK = 0;

  std::vector<std::string> strs;
  switch (rank) {
  case SEND_RANK: {
    std::vector<std::string> pack_buffs;
    std::vector<std::vector<int>> size_buffs;

    MPI_Request req;
    strs.resize(VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] = std::string("hello world ") + std::to_string(i);
    }
    MPI_PackIsend_Strings(strs, pack_buffs, size_buffs, RECV_RANK, 0,
                          MPI_COMM_WORLD, &req);

    MPI_Wait(&req, MPI_STATUS_IGNORE);
  } break;
  case RECV_RANK:
    MPI_MultiPackRecv_Strings(strs, VEC_SIZE, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected = std::string("hello world ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, Bcast) {
  // Get MPI metadata
  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::string str = "";
  switch (rank) {
  case COMM_WORLD_ROOT:
    str = "hello world";
    MPI_Bcast_String(str, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    break;
  default:
    MPI_Bcast_String(str, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("hello world").size());
    ASSERT_STREQ(str.c_str(), "hello world");
    break;
  }
}

TEST(MPI_STRING_TEST, BcastVec) {
  // Get MPI metadata
  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::vector<std::string> strs;
  switch (rank) {
  case COMM_WORLD_ROOT:
    strs.push_back("hello");
    strs.push_back("world");
    MPI_Bcast_Strings(strs, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    break;
  default:
    MPI_Bcast_Strings(strs, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), 2);
    ASSERT_EQ(strs[0].size(), std::string("hello").size());
    ASSERT_STREQ(strs[0].c_str(), "hello");
    ASSERT_EQ(strs[1].size(), std::string("world").size());
    ASSERT_STREQ(strs[1].c_str(), "world");
    break;
  }
}

TEST(MPI_STRING_TEST, PackBcastVec) {
  // Get MPI metadata
  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::vector<std::string> strs;
  switch (rank) {
  case COMM_WORLD_ROOT:
    strs.push_back("hello");
    strs.push_back("world");
    MPI_PackBcast_Strings(strs, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    break;
  default:
    MPI_PackBcast_Strings(strs, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), 2);
    ASSERT_EQ(strs[0].size(), std::string("hello").size());
    ASSERT_STREQ(strs[0].c_str(), "hello");
    ASSERT_EQ(strs[1].size(), std::string("world").size());
    ASSERT_STREQ(strs[1].c_str(), "world");
    break;
  }
}

TEST(MPI_STRING_TEST, PackIbcastVec) {
  // Get MPI metadata
  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::vector<std::string> ans;
  for (int i = 0; i < 26; i++) {
    ans.emplace_back(79 << 20, 'a' + i);
  }
  std::vector<std::string> strs;
  switch (rank) {
  case COMM_WORLD_ROOT:
    MPI_PackIbcast_Strings(ans, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    break;
  default:
    MPI_PackIbcast_Strings(strs, COMM_WORLD_ROOT, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), ans.size());
    for (int i = 0; i < strs.size(); i++) {
      ASSERT_EQ(strs[i].size(), ans[i].size());
      ASSERT_STREQ(strs[i].c_str(), ans[i].c_str());
    }
    break;
  }
}

TEST(MPI_STRING_TEST, PackAllgather) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;

  std::vector<std::string> strs;
  switch (rank) {
  default: {
    std::vector<std::string> strs(VEC_SIZE);
    for (int i = 0; i < strs.size(); i++) {
      std::string hello("hello ");
      std::string r(rank + 1, 'a' + i);
      strs[i] = hello + r;
    }

    std::vector<int> aux(strs.size());
    std::string packed_str;
    for (int i = 0; i < strs.size(); i++) {
      packed_str += strs[i];
      aux[i] = strs[i].size();
    }

    unsigned long len = packed_str.size();
    unsigned long max_len;
    MPI_Allreduce(&len, &max_len, 1, MPI_UNSIGNED_LONG, MPI_MAX,
                  MPI_COMM_WORLD);
    packed_str.resize(max_len);

    {
      std::vector<int> recv_aux;
      recv_aux.resize(np * VEC_SIZE);
      MPI_Allgather(aux.data(), VEC_SIZE, MPI_INT,
                    const_cast<int *>(recv_aux.data()), VEC_SIZE, MPI_INT,
                    MPI_COMM_WORLD);

      std::string recv_packed_str;
      recv_packed_str.resize(np * max_len);
      MPI_Allgather(packed_str.data(), max_len, MPI_CHAR,
                    const_cast<char *>(recv_packed_str.data()), max_len,
                    MPI_CHAR, MPI_COMM_WORLD);

      for (int k = 0; k < recv_aux.size(); k++) {
        int gidx = k / VEC_SIZE;
        int gstart = gidx * max_len;

        int lidx = k % VEC_SIZE;
        int lstart = 0;
        for (int i = 0; i < lidx; i++) {
          lstart += recv_aux[gidx * VEC_SIZE + i];
        }
        std::string unpacked = recv_packed_str.substr(
            gstart + lstart, recv_aux[gidx * VEC_SIZE + lidx]);
        std::string expected = "hello " + std::string(gidx + 1, 'a' + lidx);
        ASSERT_EQ(unpacked.size(), expected.size());
        ASSERT_STREQ(unpacked.c_str(), expected.c_str());
      }
    }

  } break;
  }
}

TEST(MPI_STRING_TEST, MultiPackAllgather) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr unsigned long THRESHOLD = 8;

  std::vector<std::string> strs;
  switch (rank) {
  default: {
    std::vector<std::string> strs(VEC_SIZE);
    for (int i = 0; i < strs.size(); i++) {
      std::string hello("hello ");
      std::string r(rank + 1, 'a' + i);
      strs[i] = hello + r;
    }

    std::vector<int> aux(strs.size());
    std::string packed_str;
    for (int i = 0; i < strs.size(); i++) {
      packed_str += strs[i];
      aux[i] = strs[i].size();
    }

    unsigned long len = packed_str.size();
    unsigned long max_len;
    MPI_Allreduce(&len, &max_len, 1, MPI_UNSIGNED_LONG, MPI_MAX,
                  MPI_COMM_WORLD);
    packed_str.resize(max_len);

    {
      std::vector<int> recv_aux;
      recv_aux.resize(np * VEC_SIZE);
      MPI_Allgather(aux.data(), VEC_SIZE, MPI_INT,
                    const_cast<int *>(recv_aux.data()), VEC_SIZE, MPI_INT,
                    MPI_COMM_WORLD);

      std::string recv_packed_str;
      recv_packed_str.resize(np * max_len);
      if (max_len > THRESHOLD) {
        auto package_num = max_len / THRESHOLD;
        std::vector<std::string> packages(package_num + 1);
        for (unsigned long i = 0; i < package_num; i++) {
          packages[i].resize(static_cast<unsigned long>(np) * THRESHOLD);
          MPI_Allgather(
              packed_str.data() + static_cast<unsigned long>(i) * THRESHOLD,
              THRESHOLD, MPI_CHAR, const_cast<char *>(packages[i].data()),
              THRESHOLD, MPI_CHAR, MPI_COMM_WORLD);
        }
        auto tail_count = max_len - package_num * THRESHOLD;
        packages[package_num].resize(np * tail_count);
        MPI_Allgather(packed_str.data() + package_num * THRESHOLD, tail_count,
                      MPI_CHAR,
                      const_cast<char *>(packages[package_num].data()),
                      tail_count, MPI_CHAR, MPI_COMM_WORLD);
        for (int i = 0; i < package_num + 1; i++) {
          std::cout << rank << ": " << packages[i] << std::endl;
        }

        for (unsigned long i = 0; i < np; i++) {
          for (int j = 0; j < package_num; j++) {
            std::copy(packages[j].begin() + i * THRESHOLD,
                      packages[j].begin() + (i + 1) * THRESHOLD,
                      recv_packed_str.begin() + i * max_len + j * THRESHOLD);
          }
          std::copy(packages[package_num].begin() + i * tail_count,
                    packages[package_num].begin() + (i + 1) * tail_count,
                    recv_packed_str.begin() + i * max_len +
                        package_num * THRESHOLD);
        }
      } else {
        MPI_Allgather(packed_str.data(), max_len, MPI_CHAR,
                      const_cast<char *>(recv_packed_str.data()), max_len,
                      MPI_CHAR, MPI_COMM_WORLD);
      }
      for (int k = 0; k < recv_aux.size(); k++) {
        int gidx = k / VEC_SIZE;
        int gstart = gidx * max_len;

        int lidx = k % VEC_SIZE;
        int lstart = 0;
        for (int i = 0; i < lidx; i++) {
          lstart += recv_aux[gidx * VEC_SIZE + i];
        }
        std::string unpacked = recv_packed_str.substr(
            gstart + lstart, recv_aux[gidx * VEC_SIZE + lidx]);
        std::string expected = "hello " + std::string(gidx + 1, 'a' + lidx);
        ASSERT_EQ(unpacked.size(), expected.size());
        ASSERT_STREQ(unpacked.c_str(), expected.c_str());
      }
    }

  } break;
  }
}

TEST(MPI_STRING_TEST, SendRecvReplace) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int RANK_A = 0;
  static constexpr int RANK_B = 1;

  std::string str = "";
  switch (rank) {
  case RANK_A:
    str = "hello";
    MPI_Sendrecv_String_replace(str, RANK_B, 0, RANK_B, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("world").size());
    ASSERT_STREQ(str.c_str(), "world");
    break;
  case RANK_B:
    str = "world";
    MPI_Sendrecv_String_replace(str, RANK_A, 0, RANK_A, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("hello").size());
    ASSERT_STREQ(str.c_str(), "hello");
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, SendRecvReplace_Safe) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int RANK_A = 0;
  static constexpr int RANK_B = 1;

  std::string str = "";
  switch (rank) {
  case RANK_A:
    str = "hello";
    MPI_Isendrecv_String_replace(str, RANK_B, 0, RANK_B, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("my world").size());
    ASSERT_STREQ(str.c_str(), "my world");
    break;
  case RANK_B:
    str = "my world";
    MPI_Isendrecv_String_replace(str, RANK_A, 0, RANK_A, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), std::string("hello").size());
    ASSERT_STREQ(str.c_str(), "hello");
    break;
  default:
    break;
  }
}

TEST(MPI_STRING_TEST, SendRecvReplaceVec) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int VEC_SIZE = 2;
  static constexpr int RANK_A = 0;
  static constexpr int RANK_B = 1;

  std::vector<std::string> strs(VEC_SIZE);
  switch (rank) {
  case RANK_A:
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] =
          std::to_string(RANK_A) + std::string(":hello ") + std::to_string(i);
    }
    MPI_Sendrecv_String_replace(strs, RANK_B, 0, RANK_B, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected =
          std::to_string(RANK_B) + std::string(":world ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  case RANK_B:
    for (int i = 0; i < VEC_SIZE; i++) {
      strs[i] =
          std::to_string(RANK_B) + std::string(":world ") + std::to_string(i);
    }
    MPI_Sendrecv_String_replace(strs, RANK_A, 0, RANK_A, 0, MPI_COMM_WORLD);
    ASSERT_EQ(strs.size(), VEC_SIZE);
    for (int i = 0; i < VEC_SIZE; i++) {
      auto expected =
          std::to_string(RANK_A) + std::string(":hello ") + std::to_string(i);
      ASSERT_EQ(strs[i].size(), expected.size());
      ASSERT_STREQ(strs[i].c_str(), expected.c_str());
    }
    break;
  default:
    break;
  }
}

TEST(MPI_BINARY_TEST, SendRecv) {
  // Get MPI metadata
  int rank, np;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  ASSERT_GE(np, 2);

  static constexpr int SEND_RANK = 0;
  static constexpr int RECV_RANK = 1;
  static constexpr int DATA = 1234;

  int integer;
  std::string str;
  char bytes[sizeof(int)];
  switch (rank) {
  case SEND_RANK:
    integer = DATA;
    std::copy(reinterpret_cast<char *>(&integer),
              reinterpret_cast<char *>(&integer) + sizeof(int), bytes);
    //! str += std::string(bytes); // c-string
    //! str += std::string(bytes, 0, sizeof(int)); // c-string
    str += std::string(bytes, sizeof(int));
    //* str += std::string(bytes, bytes + sizeof(int)); // True
    MPI_Send_String(str, RECV_RANK, 0, MPI_COMM_WORLD);
    break;
  case RECV_RANK:
    MPI_Recv_String(str, SEND_RANK, 0, MPI_COMM_WORLD);
    ASSERT_EQ(str.size(), sizeof(int));
    std::copy(str.data(), str.data() + sizeof(int),
              reinterpret_cast<char *>(&integer));
    ASSERT_EQ(integer, DATA);
    break;
  default:
    break;
  }
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  int ret = MPI_Init(&argc, &argv);
  EXPECT_EQ(ret, 0);

  //* // Only one printer
  //! But there are no feedbacks in other processes;
  // ::testing::TestEventListeners &listeners =
  //     ::testing::UnitTest::GetInstance()->listeners();
  // if (MPI_COMM_WORLD.Get_rank() != COMM_WORLD_ROOT) {
  //   delete listeners.Release(listeners.default_result_printer());
  // }

  // Add object that will finalize MPI on exit; Google Test owns this pointer
  ::testing::AddGlobalTestEnvironment(new GTestMPIListener::MPIEnvironment);

  // Get the event listener list.
  ::testing::TestEventListeners &listeners =
      ::testing::UnitTest::GetInstance()->listeners();

  // Remove default listener: the default printer and the default XML printer
  delete listeners.Release(listeners.default_result_printer());
  delete listeners.Release(listeners.default_xml_generator());

  // Adds MPI listener; Google Test owns this pointer
  listeners.Append(new GTestMPIListener::MPIMinimalistPrinter);

  // Run Test
  ret = RUN_ALL_TESTS();
  EXPECT_EQ(ret, 0);

  return 0;
}